package raytracing

import "math"

type Material interface {
	Scatter(r_in *Ray, rec *HitRecord) (attenuation Vec3, scattered Ray, bScattered bool)
}

type Lambertian struct {
	Albedo Vec3
}

func NewLambertian(a Vec3) *Lambertian {
	return &Lambertian{
		Albedo: a,
	}
}

func (mat *Lambertian) Scatter(r_in *Ray, rec *HitRecord) (attenuation Vec3, scattered Ray, bScattered bool) {
	scatterDirection := rec.Normal.Add(RandomUnitVector())

	if scatterDirection.NearZero() {
		scatterDirection = rec.Normal
	}

	scattered = Ray{
		Origin:    rec.P,
		Direction: scatterDirection,
	}
	attenuation = mat.Albedo
	bScattered = true

	return
}

type Metal struct {
	Albedo Vec3
	Fuzz   float64
}

func NewMetal(a Vec3, f float64) *Metal {
	return &Metal{
		Albedo: a,
		Fuzz:   f,
	}
}

func reflect(v Vec3, n Vec3) Vec3 {
	return v.Sub(n.MulScalar(v.Dot(n) * 2))
}

func (mat *Metal) Scatter(r_in *Ray, rec *HitRecord) (attenuation Vec3, scattered Ray, bScattered bool) {

	reflected := reflect(r_in.Direction.UnitVector(), rec.Normal)

	scattered = Ray{
		Origin:    rec.P,
		Direction: reflected.Add(RandomInUnitSphereVec3().MulScalar(mat.Fuzz)),
	}
	attenuation = mat.Albedo
	bScattered = scattered.Direction.Dot(rec.Normal) > 0

	return
}

func refract(uv Vec3, n Vec3, etai_over_etat float64) Vec3 {
	cos_theta := math.Min(uv.Negate().Dot(n), 1.0)
	r_out_perp := uv.Add(n.MulScalar(cos_theta)).MulScalar(etai_over_etat)
	r_out_parallel := n.MulScalar(-math.Sqrt(math.Abs(1.0 - r_out_perp.LengthSquared())))
	return r_out_perp.Add(r_out_parallel)
}

func reflectance(cosine, ref_idx float64) float64 {
	r0 := (1.0 - ref_idx) / (1.0 + ref_idx)
	r0 = r0 * r0
	return r0 + (1-r0)*math.Pow(1-cosine, 5)
}

type Dielectric struct {
	ir float64
}

func NewDielectric(ir float64) *Dielectric {
	return &Dielectric{ir}
}

func (mat *Dielectric) Scatter(r_in *Ray, rec *HitRecord) (attenuation Vec3, scattered Ray, bScattered bool) {
	// todo not work
	attenuation = Vec3{1.0, 1.0, 1.0}
	var refractionRatio float64
	if rec.FrontFace {
		refractionRatio = 1.0 / mat.ir
	} else {
		refractionRatio = mat.ir
	}

	unitDirection := r_in.Direction.UnitVector()
	cosTheta := math.Min(1.0, unitDirection.Negate().Dot(rec.Normal))
	sinTheta := math.Sqrt(1.0 - cosTheta*cosTheta)

	cannotRefract := refractionRatio*sinTheta > 1.0
	var direction Vec3

	if cannotRefract || reflectance(cosTheta, refractionRatio) > Random() {
		direction = reflect(unitDirection, rec.Normal)
	} else {
		direction = refract(unitDirection, rec.Normal, refractionRatio)
	}

	scattered = Ray{
		rec.P,
		direction,
	}
	bScattered = true

	return
}
